Load Balancing: Add a second EC2 Instance
We'll cover the following
Currently, our application is running on a single EC2 instance. To allow our application to scale beyond the capacity of a single instance, we need to introduce a load balancer that can direct traffic to multiple instances.
Adding a second instance#
We could naively add a second instance by simply duplicating the configuration we have for our existing instance. But that would create a lot of configuration duplication. Instead, we’re going to pull the bulk of the EC2 configuration into an EC2 launch template, and then we’ll simply reference the launch template from both instances.
We can almost copy our EC2 instance configuration into a new launch template resource as is, but there are slight differences between the two specifications. In addition, we’ll also need to change the cfn-init
and cfn-signal
calls at the end of the UserData
script to dynamically determine the instance ID at runtime.
Line #30: See the next code listing for how to fill in this part.
Now let’s update the UserData
script.
Line #39: We’re using the Instance Metadata service to get the instance id.
Line #41: Here, we’re getting the tags associated with this instance. The aws:cloudformation:logical-id
tag is automatically attached by CloudFormation. Its value is what we pass to cfn-signal
to signal a successful launch.
Line #41: Note the usage of ${!INSTANCE_ID}
. Since this is inside a CloudFormation !Sub
, if we used ${INSTANCE_ID}
, CloudFormation would have tried to do the substitution itself. Adding the !
tells CloudFormation to rewrite it for bash
to interpret.
Now, we can change our instance resource to reference the new launch template.
Line #10: Each time we update our launch template, it will get a new version number. We always want to use the latest.
Adding a second instance is now as easy as creating a new instance resource that references the same launch template.
We also need to add an inline IAM policy to allow the UserData
script to access the EC2 DescribeTags
API. Let’s modify the InstanceRole
resource to add it.
Line #16: This policy allows our instance to query EC2 for tags.
Now, we can change the output of our CloudFormation template to return a URL for both instances.
Finally, let’s change our deploy-infra.sh
script to give us these URLs.
If we run the deploy-infra.sh
script now, we should see a pair of URLs when the deployment finishes.
Our old instance should have been terminated, and two new instances should have started in its place.
At this point, let’s also checkpoint our progress into GitHub.
Next, let’s modify our application a little bit to allow us to see how our requests get routed. We can do this by simply including the hostname in the response message.
Line #3: Changes the message to include the hostname.
Let’s push this change to GitHub and wait for the deployment to finish.
We can follow the deployment progress from the CodePipeline console. Once the deployment is complete, we can verify our change by making a request to both URLs.
🔍 The hostname that the server is printing is not the same as the Public DNS assigned to the instance. It is actually a private domain name that EC2 assigns to each instance. You can find this private domain in the EC2 console under the Private DNS field.
Note: All the code has been already added and we are pushing it on our repository as well.
/
- deploy-infra.sh
In order to get a pictorial view of our developed cloudformation stack so far, below is the design view which shows the resources we created and their relationships.
Let’s go ahead and add a load balancer to have a single endpoint for our application in the next part of this lesson.